#define LIGHTING_DIRECT /* uncomment to enable direct lighting calculation with shadows */
#define LIGHTING_BOUNCE_ONCE /* uncomment to enable the first reflection */
//#define LIGHTING_BOUNCE_TWICE /* uncomment to enable a second reflection */

struct Slide {
	vec2 pos;
    vec2 normal;
};

#define SLIDE_COUNT 6

mat2 rotation(float theta) {
	return mat2(cos(theta), -sin(theta), sin(theta), cos(theta));  
}

float boxDistance(vec2 pos, vec2 center, vec2 normal, vec2 scale) {
    mat2 rot = mat2(normal.x, -normal.y, normal.y, normal.x);
	vec2 delta = rot * (pos - center);
    delta = clamp(delta, -scale, scale);
    vec2 clampPos = center + delta * rot;
    return length(clampPos - pos);
}

vec2 map(vec2 pos, Slide slides[SLIDE_COUNT]) {
    float dist = 100.0;
    float hit = -1.0;
    for (int i = 0; i < SLIDE_COUNT; ++i) {
     	float thisDist = boxDistance(pos, slides[i].pos, slides[i].normal, vec2(0.2, 0.01));
        if (thisDist < dist) {
         	hit = float(i);
            dist = thisDist;
        }
    }
    return vec2(dist, hit);
}

vec3 trace(vec2 origin, vec2 dir, float far, Slide slides[SLIDE_COUNT]) {
 	float t = 0.0;
    float hit = -1.0;
    for (int i = 0; i < 16; ++i) {
     	vec2 pos = origin + dir * t;
        vec2 d = map(pos, slides);
        t += d.x * 1.0;
        hit = d.y;
        if (t >= far) {
         	return vec3(t, 1.0, -1.0);   
        }
        if (d.x < 0.0) {
         	return vec3(t, 0.0, hit);   
        }
    }
	return vec3(t, 0.0, hit);
}

float reflection(vec2 a, vec2 b, float power, float fresnel, Slide slides[SLIDE_COUNT]) {
    float r = 0.0;

#ifdef LIGHTING_DIRECT
	vec2 delta = b - a;
    float len = length(delta);
	vec3 visa = trace(a, delta/len, len, slides);
	r += visa.y / (1.0 + len * len * power);
#endif

#ifdef LIGHTING_BOUNCE_ONCE
    for (int i = 0; i < SLIDE_COUNT; ++i) {
        vec2 deltai = a - slides[i].pos;
        vec2 refi = slides[i].pos - reflect(deltai, slides[i].normal);

        vec2 rayi = refi - b;
        float leni = length(rayi);
        rayi /= leni;
        vec3 pathi = trace(b, rayi, leni, slides);
        if (pathi.z != float(i)) {
            continue;
        }
		vec2 hiti = b + rayi * pathi.x;
        
        vec2 delta = hiti - a;
        float len = length(delta);
        delta /= len;
        vec3 path = trace(a, delta, len*0.99, slides);
        if (path.y == 0.0) {
            continue;   
        }

        float pathLength = leni;
        float prod = abs(dot(-normalize(delta), slides[i].normal));
        r += (1.0 - prod * fresnel) / (1.0 + pathLength * pathLength * power);
    }
#endif // LIGHTING_BOUNCE_ONCE
    
#ifdef LIGHTING_BOUNCE_TWICE
	for (int i = 0; i < SLIDE_COUNT; ++i) {
        vec2 deltai = a - slides[i].pos;
        vec2 refi = slides[i].pos - reflect(deltai, slides[i].normal);
        for (int j = 0; j < SLIDE_COUNT; ++j) {
            if (j==i) {
                continue;
            }
            
        	vec2 deltaj = refi - slides[j].pos;
        	vec2 refj = slides[j].pos - reflect(deltaj, slides[j].normal);
            
            vec2 rayj = refj - b;
            float lenj = length(rayj);
            rayj /= lenj;
            vec3 pathj = trace(b, rayj, lenj, slides);
            if (pathj.z != float(j)) {
                continue;
            }
            vec2 hitj = b + rayj * pathj.x;
            
            vec2 rayi = refi - hitj;
            float leni = length(rayi);
            rayi /= leni;
            hitj += rayi * 0.01;
            vec3 pathi = trace(hitj, rayi, leni, slides);
            if (pathi.z != float(i)) {
                continue;
            }
            vec2 hiti = rayi + hitj * pathi.x;
            
            vec2 delta = hiti - a;
            float len = length(delta);
            delta /= len;
            vec3 path = trace(a, delta, len*0.99, slides);
            if (path.y == 0.0) {
                continue;
            }

        	float pathLength = lenj;
        	float prod = abs(dot(-normalize(delta), slides[i].normal));
            prod *= abs(dot(-normalize(rayi), slides[j].normal));
        	r += (1.0 - prod * fresnel) / (1.0 + pathLength * pathLength * power);
        }
    }
#endif // LIGHTING_BOUNCE_TWICE
 
    return r;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord ){
	vec2 uv = fragCoord.xy / iResolution.xy * 2.0 - 1.0;
    uv.x *= iResolution.x / iResolution.y;

    Slide slides[SLIDE_COUNT];
    
    slides[0].pos = vec2(0.0, 0.0);
    slides[0].normal = vec2(0.0, 1.0) * rotation(-iTime*0.5);

    slides[1].pos = vec2(-0.2, -0.8);
    slides[1].normal = vec2(0.0, 1.0) * rotation(-3.14/2.5);
    
    slides[2].pos = vec2(0.3, -0.8);
    slides[2].normal = vec2(0.0, 1.0) * rotation(3.14/2.5);
    
    slides[3].pos = vec2(1.2, 0.5);
    slides[3].normal = vec2(0.0, 1.0) * rotation(3.14/8.0);
    
    slides[4].pos = vec2(-1.0, -0.2);
    slides[4].normal = vec2(1.0, 0.0);

    slides[5].pos = vec2(-1.0, 0.2);
    slides[5].normal = vec2(1.0, 0.0);
    
    vec2 lightPos = vec2(cos(iTime), sin(iTime)) * 0.6;
    
    if (iMouse.z >= 1.0) {
    	lightPos = iMouse.xy / iResolution.xy * 2.0 - 1.0;
    	lightPos.x *= iResolution.x / iResolution.y;
    }
    
    float lighting = reflection(lightPos, uv, 1.0, 0.0, slides);
    lighting = 0.3 + lighting * 0.7;
    
    vec2 delta = lightPos - uv;
    float dist = 1.0 / (1.0 + dot(delta, delta) * 100.0);
    float orb = clamp(pow(0.1 + dist,8.0),0.0,1.0);

    vec3 colour = vec3(1.0);
    colour.y *= 0.5 + 0.5 * cos(1.57 - iTime / 10.0);
    colour.z *= 0.5 + 0.5 * cos(1.57 + iTime / 20.0);
    
	vec3 tex = texture(iChannel0, uv * 2.0).xyz;
        
    vec3 ground = (tex * lighting + orb) * colour;
    
    float solid = abs(map(uv,slides).x) < 0.01 ? 1.0 : 0.0;
    
    vec3 final = mix(ground, vec3(lighting,lighting,lighting), solid);
    
    fragColor = vec4(final, 1.0);
}
